/*
 * File: Scene.cs
 * Author: Gourav Das
 * Created: April 2011
 * No Copyright
 */

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using _22yards.Core;


namespace _22yards._2D
{
    /// <summary>
    /// The Scene's transition state.
    /// </summary>
    public enum enumSceneState
    {
        Transitioning,
        IsActive,
        Transitioned,
        InActive,
    }


    /// <summary>
    /// A scene is a single screen that has its own update and draw.
    /// </summary>
    public abstract class Scene
    {
        #region Properties

        /// <summary>
        /// This property indicates whether the scene is a popup
        /// </summary>
        public bool IsPopup { get; protected set; }

        /// <summary>
        /// Time taken by the scene takes to transition when it is activated.
        /// </summary>
        public TimeSpan TransitioningTime { get; protected set; }

        /// <summary>
        /// Time taken by the scene takes to transition when it is deactivated
        /// </summary>
        public TimeSpan TransitionedTime { get; protected set; }

        /// <summary>
        /// The current position of the scenes transition, starting from zero (fully active, no transition) ending with (transitioned
        /// fully off to nothing).
        /// </summary>
        public float TransitionPosition
        {
            get { return transitionPosition; }
            protected set { transitionPosition = value; }
        }

        float transitionPosition = 1;

        /// <summary>
        /// Gets the current difference of the scenes transition, starting with 1 (fully active, no transition) to 0 (transitioned
        /// fully off to nothing).
        /// </summary>
        public float TransitionAlpha
        {
            get { return 1f - TransitionPosition; }
        }


        /// <summary>
        /// Gets the current scene transition state.
        /// </summary>
        public enumSceneState SceneState { get; protected set; }

        /// <summary>
        /// Indicates whether the screen is exiting or making room for another scene. 
        /// if true, the scene will automatically remove itself as soon as the transition finishes.
        /// </summary>
        public bool IsExiting
        {
            get;
            protected internal set;
        }

        /// <summary>
        /// Checks whether this scene is active and can respond to user input.
        /// </summary>
        public bool IsActive
        {
            get
            {
                return !otherSceneHasFocus &&
                       (SceneState == enumSceneState.Transitioning ||
                        SceneState == enumSceneState.IsActive);
            }
        }

        bool otherSceneHasFocus;


        /// <summary>
        /// Gets the engine that this scene belongs to.
        /// </summary>
        public SceneEngine sceneEngine { get; internal set; }

        /// <summary>
        /// Gets the index of the player who is currently controlling this scene,
        /// or null if it is accepting input from any player. This is used to lock
        /// the game to a specific player profile. The main menu responds to input
        /// from any connected gamepad, but whichever player makes a selection from
        /// this menu is given control over all subsequent scenes, so other gamepads
        /// are inactive until the controlling player returns to the main menu.
        /// </summary>
        public PlayerIndex? ControllingPlayer { get; internal set; }

         #endregion

        #region Initialization


        /// <summary>
        /// Load graphics content for the scene.
        /// </summary>
        public virtual void LoadContent() { }


        /// <summary>
        /// Unload content for the scene.
        /// </summary>
        public virtual void UnloadContent() { }


        #endregion

        #region Update and Draw


        /// <summary>
        /// Allows the scene to run logic, such as updating the transition position.
        /// This method is called regardless of whether the scene is active, hidden, or in the middle of a transition.
        /// </summary>
        public virtual void Update(GameTime gameTime, bool otherSceneHasFocus,
                                                      bool coveredByOtherScene)
        {
            this.otherSceneHasFocus = otherSceneHasFocus;

            if (IsExiting)
            {
                // If the screen is exiting, it should transition off.
                SceneState = enumSceneState.Transitioned;

                if (!UpdateTransition(gameTime, TransitionedTime, 1))
                {
                    // When the transition finishes, remove the scene.
                    sceneEngine.RemoveScene(this);
                }
            }
            else if (coveredByOtherScene)
            {
                // If the scene is covered by another, it should transition off.
                if (UpdateTransition(gameTime, TransitionedTime, 1))
                {
                    // Still busy transitioning.
                    SceneState = enumSceneState.Transitioned;
                }
                else
                {
                    // Transition finished!
                    SceneState = enumSceneState.InActive;
                }
            }
            else
            {
                // Otherwise the scene should transition on and become active.
                if (UpdateTransition(gameTime, TransitioningTime, -1))
                {
                    // Still busy transitioning.
                    SceneState = enumSceneState.Transitioning;
                }
                else
                {
                    // Transition finished!
                    SceneState = enumSceneState.IsActive;
                }
            }
        }


        /// <summary>
        /// Helper for updating the screen transition position.
        /// </summary>
        bool UpdateTransition(GameTime gameTime, TimeSpan time, int direction)
        {
            // How much should we move
            float transitionDelta;

            if (time == TimeSpan.Zero)
                transitionDelta = 1;
            else
                transitionDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds /
                                          time.TotalMilliseconds);

            // Update the transition position.
            TransitionPosition += transitionDelta * direction;

            // Did we reach the end of the transition?
            if (((direction < 0) && (TransitionPosition <= 0)) ||
                ((direction > 0) && (TransitionPosition >= 1)))
            {
                TransitionPosition = MathHelper.Clamp(TransitionPosition, 0, 1);
                return false;
            }

            // Otherwise we are still busy transitioning.
            return true;
        }


        /// <summary>
        /// Allows the scene to handle user input. Unlike Update, this method
        /// is only called when the screen is active, and not when some other
        /// scene has taken the focus.
        /// </summary>
        public virtual void HandleInput(InputEngine input) { }


        /// <summary>
        /// This is called when the screen should draw itself.
        /// </summary>
        public virtual void Draw(GameTime gameTime) { }


        #endregion

        #region Public Methods


        /// <summary>
        /// Tells the scene to go away. Unlike SceneEngine.RemoveScene, which
        /// instantly kills the scene, this method respects the transition timings
        /// and will give the scene a chance to gradually transition off.
        /// </summary>
        public void ExitScene()
        {
            if (TransitionedTime == TimeSpan.Zero)
            {
                // If the screen has a zero transition time, remove it immediately.
                sceneEngine.RemoveScene(this);
            }
            else
            {
                // Otherwise flag that it should transition off and then exit.
                IsExiting = true;
            }
        }


        #endregion
    }
}
